﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

using TracedReality.RayTracing;
using QiHe.Yaml.Grammar;

namespace TracedReality.Primitive
{
    /// <summary>
    /// A sphere.
    /// </summary>
    public class Sphere : Shape
    {
        /// <summary>
        /// Center of this Sphere.
        /// </summary>
        public Vector center;

        /// <summary>
        /// Radius of this Sphere.
        /// </summary>
        public double radius;

        /// <summary>
        /// Create a Sphere with this given center, radius, and Material.
        /// </summary>
        /// <param name="c">Center.</param>
        /// <param name="r">Radius.</param>
        /// <param name="m">Material.</param>
        public Sphere(Vector c, double r, ref Material m)
        {
            center = c;
            radius = r;
            material = m;
            bound = new BoundingBox(c - r, c + r);

        }

        public override bool Equals(Shape s)
        {
            if (s is Sphere) { return Equals((Sphere)s); }
            return false;
        }

        public bool Equals(Sphere s)
        {
            return center.Equals(s.center) &&
                radius == s.radius &&
                material.Equals(s.material);
        }

        public override bool Similar(Shape s)
        {
            if (!(s is Sphere)) { return false; }
            Sphere p = (Sphere)s;

            return radius == p.radius &&
                material.Equals(p.material);
        }

        /// <summary>
        /// Test if the given Ray intersects this Sphere.
        /// </summary>
        /// <param name="ray">Ray to test for intersection.</param>
        /// <param name="hit">Hit to update with intersection info.</param>
        /// <returns>1 if the Ray intersects this Sphere from the outside,
        /// -1 if the Ray intersects this Sphere from the inside,
        /// 0 if no intersection occurs.</returns>
        public override int Intersect(Ray ray, Hit hit)
        {
            Vector tCen = ray.origin - center;
            double b = 2 * tCen.dot(ray.direction);
            double c = tCen.dot(tCen) - (radius * radius);

            double d = (b * b) - (4 * c);

            if (d < 0) { return 0; }

            d = Math.Sqrt(d);

            double tmin = (-b - d) / 2.0;
            double tplu = (-b + d) / 2.0;

            double t = 0;
            if (tmin > 0)
            {
                t = tmin;
            }
            else if (tplu > 0)
            {
                t = tplu;
            }
            else { return 0; }

            if (t > Settings.EPSILON && t < hit.t)
            {
                hit.t = t;
                hit.normal = ray.pointAtParameter(t) - center;
                hit.normal.normalize();
                hit.material = material;

                return (ray.direction.dot(hit.normal) < 0 ? 1 : -1);
            }

            return 0;
        }

        public static Sphere Parse(MappingEntry entry)
        {
            if (entry == null) { throw new ArgumentNullException("entry"); }

            Mapping map = (Mapping)entry.Value;
            Material material = Material.Parse(map.Find("material"));
            MappingEntry found = map.Find("center");
            if (found == null) { throw new ArgumentException("No center entry"); }
            Vector center = Vector.FromSequence((Sequence)found.Value);
            found = map.Find("radius");
            if (found == null) { throw new ArgumentException("No radius entry"); }
            double size = double.Parse(found.Value.ToString());

            return new Sphere(center, size, ref material);
        }

        /// <summary>
        /// Get a string representation of this Sphere.
        /// </summary>
        /// <returns>A string representing this Sphere.</returns>
        public override string ToString()
        {
            return "Sphere: { center: " + center + ", radius: " + radius + ", material: " + material + " }";
        }
    }
}
